#include "stdafx.h"
#include "CppUnitTest.h"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

#include <vex/functional/any_multicast_delegate.h>
#include <vex/functional/make_any_multicast_delegate.h>
#include <vex/functional/make_any_delegate.h>
#include <vex/iterable/any_range.h>
#include <vex.tests/mstest/tests.core/tests.core.handle_policy.h>

namespace tests { namespace tests_functional_mc_any_multicast_delegate {		

    namespace vf = vex::functional;
    namespace mpl = boost::mpl;
    namespace bft = boost::function_types;

    TEST_CLASS(test_class)
    {
        BEGIN_TEST_CLASS_ATTRIBUTE()
            TEST_CLASS_ATTRIBUTE(L"Module", L"tests.functional.mc.any_multicast_delegate")
        END_TEST_CLASS_ATTRIBUTE();
    public:

        template<class TSignature>
        void any_multicast_delegate_types_are_valid_impl()
        {
            typedef bft::components<TSignature> func_comp_t;
            typedef vf::any_multicast_delegate<TSignature> delegate_t;
            typedef vf::contract::multicast_delegate<TSignature> exp_delegate_contract_t;

            // delegate contract
            Assert::IsTrue(
                typeid(exp_delegate_contract_t) ==
                typeid(typename delegate_t::contract_type), wlog_message()
            << "expected: " << typeid(exp_delegate_contract_t).name() << std::endl
            << "got: " << typeid(typename delegate_t::contract_type).name() << std::endl 
            | as_string);

            Assert::IsTrue(std::is_same<
                exp_delegate_contract_t, 
                typename delegate_t::contract_type
            >::value, wlog_message()
            << "expected: " << typeid(exp_delegate_contract_t).name() << std::endl
            << "got: " << typeid(typename delegate_t::contract_type).name() << std::endl 
            | as_string);

            // result type
            Assert::IsTrue(std::is_same<
                typename mpl::deref< typename mpl::begin<func_comp_t>::type >::type, 
                typename delegate_t::result_type
            >::value, wlog_message()
            << "expected: " << typeid(typename mpl::deref< typename mpl::begin<func_comp_t>::type >::type).name() << std::endl
            << "got: " << typeid(typename delegate_t::result_type).name() << std::endl
            | as_string);
        }

        template<class TSignature, class TRet>
        void any_multicast_delegate_types_are_valid_with_custom_return_type_impl()
        {
            typedef vf::any_multicast_delegate<TSignature, TRet> delegate_t;
            typedef vf::contract::multicast_delegate<TSignature, TRet> exp_delegate_contract_t;

            // delegate contract
            Assert::IsTrue(
                typeid(exp_delegate_contract_t) ==
                typeid(typename delegate_t::contract_type), wlog_message()
            << "expected: " << typeid(exp_delegate_contract_t).name() << std::endl
            << "got: " << typeid(typename delegate_t::contract_type).name() << std::endl 
            | as_string);

            Assert::IsTrue(std::is_same<
                exp_delegate_contract_t, 
                typename delegate_t::contract_type
            >::value, wlog_message()
            << "expected: " << typeid(exp_delegate_contract_t).name() << std::endl
            << "got: " << typeid(typename delegate_t::contract_type).name() << std::endl 
            | as_string);

            // result type
            Assert::IsTrue(std::is_same<
                TRet, 
                typename delegate_t::result_type
            >::value, wlog_message()
            << "expected: " << typeid(TRet).name() << std::endl
            << "got: " << typeid(typename delegate_t::result_type).name() << std::endl
            | as_string);
        }

        TEST_METHOD(any_multicast_delegate_implements_handle_policy)
        {
            {
                vf::any_multicast_delegate<void ()> l, r;
                handle_policy::correctly_implements_handle_policy(l, r);
            }

            {
                vf::any_multicast_delegate<void (int, double)> l, r;
                handle_policy::correctly_implements_handle_policy(l, r);
            }

            {
                vf::any_multicast_delegate<void (int, double, std::string const &)> l, r;
                handle_policy::correctly_implements_handle_policy(l, r);
            }
        }

        TEST_METHOD(any_multicast_delegate_type_can_be_constructed_from_nullary_signature)
        {
            {
                this->any_multicast_delegate_types_are_valid_impl<
                    void ()>();
                vf::any_multicast_delegate<void ()> d;
                Assert::IsTrue(std::is_same<void, decltype(d())>::value);
            }

            {
                this->any_multicast_delegate_types_are_valid_with_custom_return_type_impl<
                    void (), int>();
                vf::any_multicast_delegate<void (), int> d;
                Assert::IsTrue(std::is_same<int, decltype(d())>::value);
            }
        }

        TEST_METHOD(any_multicast_delegate_type_can_be_constructed_from_supported_signatures)
        {
            this->any_multicast_delegate_types_are_valid_impl<
                void ()>();
            this->any_multicast_delegate_types_are_valid_impl<
                int ()>();
            this->any_multicast_delegate_types_are_valid_impl<
                vf::contract::delegate<int()>* ()>();

            this->any_multicast_delegate_types_are_valid_impl<
                void (int, double)>();
            this->any_multicast_delegate_types_are_valid_impl<
                void (int, double, std::shared_ptr<double> const &)>();
            this->any_multicast_delegate_types_are_valid_impl<
                void (int, double, std::shared_ptr<double> const &, std::string &)>();
            this->any_multicast_delegate_types_are_valid_impl<
                void (int, double, std::shared_ptr<double> const &, std::string &, std::vector<int>&&)>();

            this->any_multicast_delegate_types_are_valid_with_custom_return_type_impl<
                void (), int>();
            this->any_multicast_delegate_types_are_valid_with_custom_return_type_impl<
                int (), void>();
            this->any_multicast_delegate_types_are_valid_with_custom_return_type_impl<
                vf::contract::delegate<int()>* (), std::exception*>();

            this->any_multicast_delegate_types_are_valid_with_custom_return_type_impl<
                void (int, double), int>();
            this->any_multicast_delegate_types_are_valid_with_custom_return_type_impl<
                void (int, double, std::shared_ptr<double> const &), int>();
            this->any_multicast_delegate_types_are_valid_with_custom_return_type_impl<
                void (int, double, std::shared_ptr<double> const &, std::string &), int>();
            this->any_multicast_delegate_types_are_valid_with_custom_return_type_impl<
                void (int, double, std::shared_ptr<double> const &, std::string &, std::vector<int>&&), std::string>();
        }

        TEST_METHOD(any_multicast_delegate_call_operator_can_be_invoked_with_proper_arguments)
        {
            {
                auto d = vex::functional::make_any_multicast_delegate<void ()>();
                Assert::IsTrue(std::is_same<void, decltype(d())>::value);
            }

            {
                auto r = vex::functional::default_reducer(3);
                typedef decltype(r) reducer_type;
                Assert::IsTrue(std::is_same<int, reducer_type::result_type>::value);
                Assert::IsTrue(std::is_same<int, vex::functional::any::reducer_result<reducer_type>::type>::value);

                auto d = vex::functional::make_any_multicast_delegate<int ()>(
                    vex::functional::default_reducer(3));
                Assert::IsTrue(std::is_same<int, decltype(d())>::value);
            }

            {
                vf::any_multicast_delegate<int (std::string const&)> d;
                Assert::IsTrue(std::is_same<int, decltype(d(std::declval<std::string const &>()))>::value);
            }

            {
                vf::any_multicast_delegate<int (std::string const&, int, double, std::shared_ptr<int> const &)> d;
                Assert::IsTrue(std::is_same<int, decltype(d(
                    std::declval<std::string const &>(),
                    std::declval<int>(),
                    std::declval<double>(),
                    std::declval<std::shared_ptr<int> const &>()
                    ))>::value);
            }
        }

        TEST_METHOD(any_multicast_delegate_with_custom_return_type_call_operator_can_be_invoked_with_proper_arguments)
        {
            {
                vf::any_multicast_delegate<void (), int> d;
                Assert::IsTrue(std::is_same<int, decltype(d())>::value);
            }

            {
                auto d = vex::functional::make_any_multicast_delegate<void ()>(
                    vex::functional::default_reducer());
                Assert::IsTrue(std::is_same<void, decltype(d())>::value);
            }

            {
                vf::any_multicast_delegate<int (std::string const&), void> d;
                Assert::IsTrue(std::is_same<void, decltype(d(std::declval<std::string const &>()))>::value);
            }

            {
                auto d = vex::functional::make_any_multicast_delegate<int (std::string const&)>(
                    vex::functional::default_reducer(42));
                Assert::IsTrue(std::is_same<int, decltype(d(std::declval<std::string const &>()))>::value);
            }


            {
                vf::any_multicast_delegate<int (std::string const&, int, double, std::shared_ptr<int> const &), void> d;
                Assert::IsTrue(std::is_same<void, decltype(d(
                    std::declval<std::string const &>(),
                    std::declval<int>(),
                    std::declval<double>(),
                    std::declval<std::shared_ptr<int> const &>()
                    ))>::value);
            }
        }

        template<class TSignature, class TFunc>
        auto make_multicast_delegate_contract(TFunc&&)
            -> vf::contract::multicast_delegate<TSignature>*
        {
            return nullptr;
        }

        template<class TSignature, class TFunc>
        auto make_any_multicast_delegate(TFunc&& p_f)
            -> vf::any_multicast_delegate<TSignature>
        {
            return vex::as_any_from_output() |
                this->make_multicast_delegate_contract<TSignature>(std::forward<TFunc>(p_f));
        }

        static void test_foo() {}
        void test_foo_member() {}

        TEST_METHOD(any_multicast_delegate_instance_can_be_constructed_from_callable)
        {
            { // standalone function
                typedef void (signature_t)();
                auto mcd = this->make_any_multicast_delegate<signature_t>(test_foo);
                typedef decltype(mcd) any_mcd_t;
                Assert::IsTrue(std::is_same<void, any_mcd_t::result_type>::value);
                
                Assert::IsTrue(std::is_convertible<
                    any_mcd_t::contract_type*, 
                    vf::contract::multicast_delegate<signature_t>*
                >::value);

                Assert::IsTrue(std::is_convertible<
                    any_mcd_t::contract_type*, 
                    vf::contract::delegate<signature_t>*
                >::value);
            }

            { // lambda with closure
                typedef void (signature_t)();
                auto mcd = this->make_any_multicast_delegate<signature_t>([=]() { this->test_foo_member(); });
                typedef decltype(mcd) any_mcd_t;
                Assert::IsTrue(std::is_same<void, any_mcd_t::result_type>::value);
                
                Assert::IsTrue(std::is_convertible<
                    any_mcd_t::contract_type*, 
                    vf::contract::multicast_delegate<signature_t>*
                >::value);

                Assert::IsTrue(std::is_convertible<
                    any_mcd_t::contract_type*, 
                    vf::contract::delegate<signature_t>*
                >::value);
            }

            { // functor
                typedef void (signature_t)();
                auto mcd = this->make_any_multicast_delegate<signature_t>(
                    std::bind(&test_class::test_foo_member, this));
                typedef decltype(mcd) any_mcd_t;
                Assert::IsTrue(std::is_same<void, any_mcd_t::result_type>::value);
                
                Assert::IsTrue(std::is_convertible<
                    any_mcd_t::contract_type*, 
                    vf::contract::multicast_delegate<signature_t>*
                >::value);

                Assert::IsTrue(std::is_convertible<
                    any_mcd_t::contract_type*, 
                    vf::contract::delegate<signature_t>*
                >::value);
            }
        }

        TEST_METHOD(addition_of_delegates_should_yield_new_multicast_delegates)
        {
            typedef std::vector< vex::functional::any_delegate<void ()> > delegate_vec_t;
            delegate_vec_t t_delegates;

            int t_value = 0;
            std::generate_n(std::back_inserter(t_delegates), 5, [&]() -> vex::any_delegate<void ()> {
                return vex::functional::make_any_delegate<void ()>([&]() { t_value += 1; });
            });

            auto mcd = vex::functional::make_any_multicast_delegate<void ()>();
            Assert::IsTrue(mcd);

            for (auto d : t_delegates) {
                mcd = vex::as_any_from_output() | mcd->combine(vex::as_raw_in(d));
                Assert::IsTrue(mcd);
            }

            auto t_inv_list = vex::as_any_from_output()
                | mcd->get_invocation_list();

            delegate_vec_t t_act_delegates;
            Assert::IsTrue(t_inv_list);
            while (t_inv_list->move_front()) {
                t_act_delegates.push_back(vex::as_any_from_output() | t_inv_list->front());
            }

            Assert::AreEqual(t_delegates.size(), t_act_delegates.size());
            Assert::IsTrue(std::equal(t_delegates.begin(), t_delegates.end(), t_act_delegates.begin()));
        }
    };

}}